﻿#ifndef ___PCLIB_S_FTP___
#define ___PCLIB_S_FTP___


#include "../m_core/m_core.hpp"
#include "s_tcp.hpp"


/**
*@brief      Ftp客户端通用接口
*/
class CPCFtpClientBase : CPCNoCopyable
{
public:
    /**
    *@brief  下载单个文件/上传单个文件结果的回调函数
    *@param  src    [in]    源路径
    *@param  dest   [in]    目的路径
    *@param  result [in]    操作结果
    *@return 返回成功表示继续操作，返回失败表示中断操作
    */
    typedef std::function<CPCResult<>(const char* src, const char* dest, const CPCResult<>& result)> PCFtpCallBack;

public:
    //ftp接口
    virtual void      FtpClose() = 0;
    virtual CPCResult<> FtpLogin(const char *pszHost, unsigned short nPort, const char* pszUser,
        const char* pszPwd, const char* pszAcc, int nTimeOut = 30000) = 0;

    virtual CPCResult<> FtpMakeDir(const char* pszRemotePath, int nTimeOut = 30000) = 0;
    virtual CPCResult<> FtpDelDir(const char* pszRemotePath, int nTimeOut = 30000) = 0;
    virtual CPCResult<> FtpDelFile(const char* pszRemotePath, int nTimeOut = 30000) = 0;
    virtual CPCResult<std::string> FtpList(const char* pszRemotePath, int nTimeOut = 30000) = 0;
    virtual CPCResult<> FtpGet(const char* pszRemotePath, const char* pszLocalPath, int nTimeOut = 30000, uint64_t nFileSize = 0) = 0;
    virtual CPCResult<> FtpPut(const char* pszLocalPath, const char* pszRemotePath, int nTimeOut = 30000) = 0;
    virtual CPCResult<> FtpGetDir(const char* pszRemotePath, const char* pszLocalPath, PCFtpCallBack callback, int nTimeOut = 30000) = 0;
    virtual CPCResult<> FtpPutDir(const char* pszLocalPath, const char* pszRemotePath, PCFtpCallBack callback, int nTimeOut = 30000) = 0;

public:
    //"当前目录"系统
    CPCFtpClientBase()
        :m_port(0)
        , m_currDir("/")
    {}
    virtual ~CPCFtpClientBase()
    {}

    /**
    *@brief  获取当前目录
    */
    std::string FtpPWD() const
    {
        return m_currDir;
    }

    /**
    *@brief  设置当前目录
    */
    bool FtpCWD(const char* pszRemotePath)
    {
        if (pszRemotePath == NULL || pszRemotePath[0] == 0)
            return false;
        m_currDir = pszRemotePath;
        if (m_currDir[m_currDir.length() - 1] != '/')
            m_currDir += '/';
        return true;
    }

    /**
    *@brief 根据当前目录将一个remotePath转化为绝对路径
    *@param pszRemotePath   [in]    输入的路径
    *@param isDir           [in]    输入路径是文件夹还是文件
    */
    std::string FtpGetAbsPath(const char* pszRemotePath, bool isDir)
    {
        if (pszRemotePath == NULL || pszRemotePath[0] == 0)
            return "";

        std::string ret;
        if (pszRemotePath[0] == '/')
            ret = pszRemotePath;
        else
            ret = m_currDir + pszRemotePath;
        if (isDir && !ret.empty() && ret[ret.length() - 1] != '/')
            ret += '/';
        return ret;
    }

protected:
    std::string m_host;
    unsigned short m_port;
    std::string m_username;
    std::string m_password;
    std::string m_account;

private:
    std::string m_currDir;
};

/**
*@brief  ftp所有回应码
*        1xy 预备状态；2xy 完成状态； 3xy 中间状态； 4xy 暂时拒绝状态；5xy 永久拒绝状态
*/
#define PCFTPCODE_RESTARTMARK   110        //新文件指示器上的重启标记
#define PCFTPCODE_SERVOKAFTERM  120        //服务将在nnn分钟后准备完成
#define PCFTPCODE_STARTTRANS    125        //打开数据连接，开始传输
#define PCFTPCODE_OPENCONNECT   150        //打开连接
#define PCFTPCODE_SUCC          200        //成功
#define PCFTPCODE_UNSUPPORTED   202        //命令没有实现,对本站点冗余
#define PCFTPCODE_SYSSTAT       211        //系统状态回复
#define PCFTPCODE_DIRSTAT       212        //目录状态回复
#define PCFTPCODE_FILESTAT      213        //文件状态回复
#define PCFTPCODE_HELPINFO      214        //帮助信息回复
#define PCFTPCODE_NAMESYSTYPE   215        //系统类型回复
#define PCFTPCODE_SERVNEWUSER   220        //接收新用户准备完成
#define PCFTPCODE_EXITNET       221        //退出网络
#define PCFTPCODE_OPENDATACON   225        //打开数据连接
#define PCFTPCODE_EXITDATACON   226        //结束数据连接
#define PCFTPCODE_PASVMODE      227        //进入被动模式(IP 地址、ID 端口)
#define PCFTPCODE_LOGINOK       230        //登录成功
#define PCFTPCODE_FILEOK        250        //文件行为完成
#define PCFTPCODE_DIRCREATED    257        //路径名建立
#define PCFTPCODE_NEEDPASS      331        //要求密码
#define PCFTPCODE_NEEDACCOUNT   332        //要求帐号
#define PCFTPCODE_FILENEEDINFO  350        //请求文件动作需要进一步的信息
#define PCFTPCODE_SERVNOTAVAIL  421        //服务不可用，关闭，如果服务知道它必须关闭，应该以421作为任何命令的响应代码
#define PCFTPCODE_CANNOTOPENCON 425        //无法打开数据连接
#define PCFTPCODE_ABRTDATACON   426        //结束连接
#define PCFTPCODE_FILENOTTAKEN  450        //请求文件动作没有执行。文件不可使用.例如，文件忙
#define PCFTPCODE_LOCALERROR    451        //请求动作放弃，处理中发生本地错误
#define PCFTPCODE_DISK          452        //磁盘空间不足
#define PCFTPCODE_INVALIDCMD    500        //无效命令
#define PCFTPCODE_INVALIDPARAM  501        //错误参数
#define PCFTPCODE_CMDNOTIMPL    502        //命令没有实现
#define PCFTPCODE_ERRORSEQ      503        //命令顺序错误
#define PCFTPCODE_ERRCMDPARAM   504        //无效命令参数
#define PCFTPCODE_NOTLOGIN      530        //未登录网络
#define PCFTPCODE_STORENEEDACC  532        //存储文件需要帐号
#define PCFTPCODE_FILEINVALID   550        //请求的动作没有执行 文件不可用(例如，没有找到文件，没有访问权限)
#define PCFTPCODE_UNKNOWNPAGE   551        //请求动作放弃，不知道的页类型
#define PCFTPCODE_STORE         552        //超过存储分配
#define PCFTPCODE_FILENAME      553        //文件名不允许

/**
*@brief      Ftp客户端类，参考rfc959实现
*            https://tools.ietf.org/html/rfc959
*            扩展标准，rfc3659
*            http://tools.ietf.org/html/rfc3659
*/
class CPCFtpClient : public CPCFtpClientBase
{
public:
    /**
    *@brief        构造ftp客户端，构造时需指定使用主动/被动模式。主动模式的端口范围不能设置得太小，否则会导致服务器425回应增多
    *@param        bPortMode   [in]    是否要设定为主动模式，否则为被动模式(默认)
    *@param        nPortMix    [in]    主动模式监听端口的最小值，如果为被动模式，忽略此参数
    *@param        nPortMax    [in]    主动模式监听端口的最大值，如果为被动模式，忽略此参数
    */
    explicit CPCFtpClient(bool bPortMode = false, unsigned short nPortMix = 1024, unsigned short nPortMax = 65535)
        :m_hCmdSocketPtr(std::make_shared<CPCSocketHandle>(true, &CPCPoller::obj()))
        , m_hDataSocketPtr(std::make_shared<CPCSocketHandle>(!bPortMode, &CPCPoller::obj()))
        , m_bPortMode(bPortMode)
        , m_nPortMix(nPortMix)
        , m_nPortMax(nPortMax)
    {
        m_port = 0;
    }
    ~CPCFtpClient()
    {
        FtpClose();
    }

    /**
    *@brief     关闭
    */
    void FtpClose() override
    {
        if (m_hCmdSocketPtr)
            m_hCmdSocketPtr->XClose();
        if (m_hDataSocketPtr)
            m_hDataSocketPtr->XClose();
    }

    /**
    *@brief     登录ftp服务器
    *@param     pszHost      [in]    ftp服务器ip地址
    *@param     nPort        [in]    ftp服务器命令连接端口
    *@param     pszUser      [in]    用户名 anonymous
    *@param     pszPwd       [in]    密码
    *@param     pszAcc       [in]    账户，一般不必给出。某些站点可能需要账户来登录
    *@param     nTimeOut     [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。 所有动作全部成功才会返回成功
    */
    CPCResult<> FtpLogin(const char *pszHost = "localhost", unsigned short nPort = 21,
        const char* pszUser = "anonymous", const char* pszPwd = "", const char* pszAcc = "",
        int nTimeOut = 30000) override
    {
        CPCResult<> result;
        m_host = (pszHost != NULL) ? pszHost : "";
        m_port = nPort;
        m_username = (pszUser != NULL) ? pszUser : "";
        m_password = (pszPwd != NULL) ? pszPwd : "";
        m_account = (pszAcc != NULL) ? pszAcc : "";

        FtpClose();
        int retCode = FtpReLogin_Only(nTimeOut);
        if (retCode / 100 != 2)
        {
            return result.SetFail(retCode,"FtpReLogin_Only fail:%s",m_LastMsg.c_str());
        }
        return result;
    }

    /**
    *@brief     创建远程目录。目录如果已经存在直接返回成功
    *@param     pszRemotePath    [in]    远程目录路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpMakeDir(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        //文件夹是否已经存在
        CPCResult<> result;
        std::string path = FtpGetAbsPath(pszRemotePath, true);
        int retCode = FtpCmdOnce("MKD", path.c_str(), nTimeOut);
        if (retCode / 100 != 2 && retCode != PCFTPCODE_FILEINVALID)
        {
            return result.SetFail(retCode,"MKD fail:%s",m_LastMsg.c_str());
        }
        return result;
    }

    /**
    *@brief     删除远程空目录。
    *@param     pszRemotePath    [in]    远程目录路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpDelDir(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        CPCResult<> result;
        std::string path = this->FtpGetAbsPath(pszRemotePath, true);
        int retCode = FtpCmdOnce("RMD", path.c_str(), nTimeOut);
        if (retCode / 100 != 2)
        {
            return result.SetFail(retCode,"RMD fail:%s",m_LastMsg.c_str());
        }
        return result;
    }

    /**
    *@brief     删除远程文件。
    *@param     pszRemotePath    [in]    远程文件路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpDelFile(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        CPCResult<> result;
        std::string path = this->FtpGetAbsPath(pszRemotePath, false);
        int retCode = FtpCmdOnce("DELE", path.c_str(), nTimeOut);
        if (retCode / 100 != 2)
        {
            return result.SetFail(retCode,"DELE fail:%s",m_LastMsg.c_str());
        }
        return result;
    }

    /**
    *@brief     获取远程文件列表
    *@param     pszRemotePath   [in]    远程目录路径，如果为空，则用当前路径
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。成功返回字符串
    */
    CPCResult<std::string> FtpList(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        CPCResult<std::string> result;
        std::string list;
        std::string path = this->FtpGetAbsPath(pszRemotePath, true);
        int retCode = FtpDataTransfer("MLSD", path.c_str(), nTimeOut, [&nTimeOut, &list](const CPCSocketHandlePtr& dataSockPtr)->CPCResult<>
        {
            CPCResult<> res;
            while (1)
            {
                CPCBuffer buf(2048);
                res = dataSockPtr->XRecv(nTimeOut, buf);
                if (!res)
                {
                    break;
                }
                list.append(buf.data(), buf.length());
            }
            return res.SetSucc();
        });
        if (retCode / 100 != 2 && retCode / 100 != 1)
        {
            return result.SetFail(retCode,"FtpDataTransfer fail:%s",m_LastMsg.c_str());
        }
#if defined(PCOS_WIN)
        std::string strU8Out;
        auto result3 = CPCConvertHelper::PCUtf8ToGb18030(strU8Out, list.data(), (int)list.length());
        if (!result3)
            strU8Out = std::move(list);
        return result.SetSucc(std::move(strU8Out));
#else
        return result.SetSucc(std::move(list));
#endif
    }

    /**
    *@brief     下载远程文件到本地
    *@param     pszRemotePath   [in]    远程文件路径
    *@param     pszLocalPath    [in]    本地文件路径
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     nFileSize       [in]    要传输的远程文件大小，传入0表示自动获取
    *@return    是否成功。
    */
    CPCResult<> FtpGet(const char* pszRemotePath, const char* pszLocalPath, int nTimeOut = 30000, uint64_t nFileSize = 0) override
    {
        //新增创建文件目录
        auto result = CPCFileUtil::PCCheckAndCreateDir(pszLocalPath);
        if (!result)
        {
            return result;
        }
        FILE* fp = fopen(pszLocalPath, "wb");
        if (fp == NULL)
        {
            return result.SetSystemFail();
        }
        CPCAutoObj autoFile([&fp](){PCCloseFILE(fp); });

        std::string pFullRemotePath = this->FtpGetAbsPath(pszRemotePath, false);
        if (nFileSize == 0)
        {
            int retCode = this->FtpCmdOnce("SIZE", pFullRemotePath.c_str(), nTimeOut);
            if (retCode != PCFTPCODE_FILESTAT)
            {
                return result.SetFail(retCode,"SIZE fail:%s",m_LastMsg.c_str());
            }
            nFileSize = CPCStrUtil::StringToUInt64(m_LastMsg.c_str() + 3);
        }

        uint64_t localSize = 0;
        int retCode = FtpDataTransfer("RETR", pFullRemotePath.c_str(), nTimeOut, [&fp, &localSize](const CPCSocketHandlePtr& dataSockPtr)->CPCResult<>
        {
            CPCResult<> res;
            while (1)
            {
                CPCBuffer buf(PC_MAXSTACK);
                res = dataSockPtr->XRecv(-1, buf);
                if (!res)
                {
                    break;
                }
                res = CPCFileUtil::PCWriteFile(fp, (const unsigned char*)buf.data(), buf.length());
                if (!res)
                {
                    break;
                }
                localSize += buf.length();
            }
            return res.SetSucc();
        });
        if (retCode / 100 != 2 && retCode / 100 != 1)
        {
            return result.SetFail(retCode,"FtpDataTransfer fail:%s",m_LastMsg.c_str());
        }
        if (nFileSize != localSize)
        {
            return result.SetFail("remote file(%s) size(%s) != download file size(%s).",
                pszRemotePath, CPCStrUtil::UInt64ToString(nFileSize).c_str(), CPCStrUtil::UInt64ToString(localSize).c_str());
        }
        return result.SetSucc();
    }

    /**
    *@brief     上传本地文件
    *@param     pszLocalPath    [in]    本地文件路径
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     pszRemotePath   [in]    远程文件路径，如果为空，则用本地文件名上传到远程目录的当前路径
    *@return    是否成功。
    */
    CPCResult<> FtpPut(const char* pszLocalPath, const char* pszRemotePath, int nTimeOut = 30000) override
    {
        CPCResult<> result;
        auto result2 = CPCFileUtil::PCSeperatePath(pszLocalPath);
        if(!result2)
        {
            return result.SetFail("PCSeperatePath fail:%s",result2.ErrDesc());
        }
        if (::access(pszLocalPath, F_OK) != 0)
        {
            return result.SetSystemFail();
        }

        std::string remoteFile;
        if (pszRemotePath == NULL || pszRemotePath[0] == 0)
        {
            //remote为空，则直接用本机文件名进行上传
            remoteFile = result2.Get().second;
        }
        else
        {
            //remote不为空，判断是不是相对路径。如果是，则转化为绝对路径
            std::string path = this->FtpGetAbsPath(pszRemotePath, false);
            result2 = CPCFileUtil::PCSeperatePath(path.c_str());
            if(!result2)
            {
                return result.SetFail("PCSeperatePath fail:%s",result2.ErrDesc());
            }
            std::string remoteDir = result2.Get().first;
            remoteFile = result2.Get().second;

            //确保路径存在并切换到这个绝对路径
            result = FtpMakeDir(remoteDir.c_str(), nTimeOut);
            if (!result)
            {
                return result;
            }
            int retCode = this->FtpCmdOnce_Only("CWD", remoteDir.c_str(), nTimeOut);
            if (retCode < 0)
            {
                return result.SetFail(retCode,"CWD fail:%s",m_LastMsg.c_str());
            }
        }

        int retCode =  FtpDataTransfer("STOR", remoteFile.c_str(), nTimeOut,
            [&pszLocalPath, &nTimeOut](const CPCSocketHandlePtr& dataSockPtr)->CPCResult<>
        {
            auto tmpTimeOut = nTimeOut;
            auto tmpDataSockPtr = dataSockPtr;
            return CPCFileUtil::PCScanFile(pszLocalPath, [&tmpDataSockPtr, &tmpTimeOut](const char* buf, size_t len)->CPCResult<>
            {
                return tmpDataSockPtr->XSend(buf, len, tmpTimeOut);
            });
        });
        if (retCode / 100 != 2 && retCode / 100 != 1)
        {
            return result.SetFail(retCode,"FtpDataTransfer fail:%s",m_LastMsg.c_str());
        }
        return result;
    }

    /**
    *@brief     下载远程目录到本地
    *@param     pszRemotePath   [in]    远程目录路径
    *@param     pszLocalPath    [in]    本地目录路径
    *@param     callback        [in]    操作文件后回调
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpGetDir(const char* pszRemotePath, const char* pszLocalPath, PCFtpCallBack callback, int nTimeOut = 30000) override
    {
        std::string strRemotePath = this->FtpGetAbsPath(pszRemotePath, true);
        std::string strLocalPath = (pszLocalPath == NULL) ? "" : pszLocalPath;
        CPCFileUtil::PCDirFormat(strLocalPath);
        CPCFileUtil::PCCreateDir(strLocalPath.c_str());

        CPCResult<> result;
        auto result2 = this->FtpList(pszRemotePath, nTimeOut);
        if(!result2)
        {
            return result.SetFail("FtpList fail:%s",result2.ErrDesc());
        }
        std::vector<std::string> lines = CPCStrUtil::PCStrSplit(result2.Get().c_str(), "\r\n");
        for (auto it = lines.begin(); it != lines.end(); ++it)
        {
            bool isDir = false;
            uint64_t nRemoteFileSize = 0;
            const char* pRemoteFileName = NULL;

            const char* pLine = (*it).c_str();
            std::vector<std::string> items = CPCStrUtil::PCStrSplit(pLine, ";");
            if (items.size() < 3)
            {
                return result.SetFail("items.size() < 3 : %s", pLine);
            }
            for (size_t i = 0; i < items.size() - 1; i++)
            {
                const char* pItem = items[i].c_str();
                if (CPCStrUtil::PCStrIsStartWith(pItem, "type=", true) &&
                    strstr(pItem, "=dir"))
                {
                    isDir = true;
                    continue;
                }

                if (CPCStrUtil::PCStrIsStartWith(pItem, "size=", true))
                {
                    nRemoteFileSize = CPCStrUtil::StringToUInt64(pItem + 5);
                    continue;
                }
            }
            pRemoteFileName = items[items.size() - 1].c_str();
            if (pRemoteFileName[0] == ' ')
                pRemoteFileName++;

            if (pRemoteFileName[0] == 0 || pRemoteFileName[0] == '.' || strcmp(pRemoteFileName, "..") == 0)
                continue;

            std::string remoteFile = strRemotePath + pRemoteFileName;
            std::string localFile = strLocalPath + pRemoteFileName;

            if (isDir)
            {
                result = this->FtpGetDir(remoteFile.c_str(), localFile.c_str(), callback, nTimeOut);
                if (!result)
                {
                    return result;
                }
            }
            else
            {
                result = this->FtpGet(remoteFile.c_str(), localFile.c_str(), nTimeOut, nRemoteFileSize);
                if (!callback(remoteFile.c_str(), localFile.c_str(), result))
                {
                    return result;
                }
            }
        }
        return result;
    }

    /**
    *@brief     上传本地目录到远程
    *@param     pszLocalPath    [in]    本地目录路径
    *@param     pszRemotePath   [in]    远程目录路径，如果为空，则用本地文件目录上传到远程目录的当前路径
    *@param     callback        [in]    操作文件后回调
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpPutDir(const char* pszLocalPath, const char* pszRemotePath, PCFtpCallBack callback, int nTimeOut = 30000) override
    {
        std::string strRemotePath = this->FtpGetAbsPath(pszRemotePath, true);
        std::string strLocalPath = (pszLocalPath == NULL) ? "" : pszLocalPath;
        CPCFileUtil::PCDirFormat(strLocalPath);
        this->FtpMakeDir(strRemotePath.c_str(), nTimeOut);

        return CPCFileUtil::PCScanDir(pszLocalPath, [&](const char* pszPath, const char* pszFileName, bool bDir)->CPCResult<>
        {
            CPCResult<> result;
            if (strLocalPath.length() >= strlen(pszPath))
            {
                return result;
            }
            std::string remoteFile = strRemotePath + (pszPath + strLocalPath.length());
            CPCStrUtil::PCReplaceAll(remoteFile, "\\", "/");
            if (!bDir)
            {
                result = FtpPut(pszPath, remoteFile.c_str(), nTimeOut);
                if (!callback(pszPath, remoteFile.c_str(), result))
                {
                    return result;
                }
            }
            else
            {
                result = this->FtpMakeDir(remoteFile.c_str(), nTimeOut);
            }
            return result;
        }, true);
    }

    /**
    *@brief     进行数据传输的命令。如LIST，STOR, RETR
    *@param     cmd         [in]    命令数据，如果为空，表示不发只收
    *@param     param       [in]    命令参数，可以为空
    *@param     nTimeOut    [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     func        [in]    数据通道准备好之后的传输动作
    *@return    ftp结果码（如果其他错误，结果码为-1），其他更详细的信息在m_LastMsg
    */
    int FtpDataTransfer(const char* cmd, const char* param, int nTimeOut, std::function<CPCResult<>(const CPCSocketHandlePtr& dataSockPtr)> func)
    {
        auto retCode = FtpCmdOnce("TYPE I", NULL, nTimeOut);
        if (retCode < 0)
        {
            return retCode;
        }
        bool bTransfered = false;
        if (!m_bPortMode)
        {
            retCode = FtpCmdOnce("PASV", NULL, nTimeOut);
            if (PCFTPCODE_PASVMODE != retCode)
            {
                return retCode;
            }
            std::string pasvDetail = m_LastMsg;
            if (pasvDetail.length() < 10)
            {
                return -1;
            }
            CPCSockAddr pasvAddr;
            auto result2 = pasvAddr.FromFtpString(pasvDetail.c_str() + 4);
            if (!result2)
            {
                m_LastMsg = result2.ErrDesc();
                return -1;
            }

            {
                auto result3 = m_hDataSocketPtr->XConnect(pasvAddr, nTimeOut);
                if (!result3)
                {
                    m_LastMsg = result3.ErrDesc();
                    return -1;
                }
                CPCAutoObj autoClose([this]() {m_hDataSocketPtr->XClose(); });

                retCode = FtpCmdOnce_Only(cmd, param, nTimeOut);
                if (retCode < 0)
                {
                    return retCode;
                }
                if (retCode / 100 != 2 && retCode / 100 != 1)
                {
                    return retCode;                        
                }
                bTransfered = strstr(m_LastMsg.c_str(), "\r\n226");

                auto result4 = func(m_hDataSocketPtr);
                if (!result4)
                {
                    m_LastMsg = result4.ErrDesc();
                    return -1;
                }
            }

            if (bTransfered)
                return retCode;
            return this->FtpCmdOnce_Only(NULL, NULL, 3000);
        }
        else
        {
            //在允许的端口范围内做有限次数的尝试
            for (int i = 0; i < PC_MIN(10, m_nPortMax - m_nPortMix + 1); i++)
            {
                unsigned int port = CPCRandom::PCGenRangeAt(m_nPortMix, m_nPortMax);
                CPCSockAddr portAddr(m_LocalIp.c_str(), port);
                std::string portParam = portAddr.GetFtpString();

                retCode = FtpCmdOnce("PORT", portParam.c_str(), nTimeOut);
                if (retCode / 100 != 2)
                {
                    return retCode;
                }

                //建立监听服务
                CPCSocket m_listensocket;
                auto result2 = m_listensocket.TcpListen(portAddr);
                if (!result2)
                {
                    continue;
                }
                CPCAutoObj autoCloseListen([&m_listensocket]() {m_listensocket.ShutdownSocket(); });

                retCode = FtpCmdOnce_Only(cmd, param, nTimeOut);
                if (retCode / 100 != 2 && retCode / 100 != 1)
                {
                    return retCode;
                }
                bTransfered = strstr(m_LastMsg.c_str(), "\r\n226");

                {
                    CPCSockAddr addrClient;
                    int clientFd = m_listensocket.Accept(addrClient, 5000);
                    if (INVALID_SOCKET == clientFd)
                    {
                        m_LastMsg = PCGetSysErrorMsg((int)PCGetLastError());
                        return -1;
                    }
                    auto result2 = m_hDataSocketPtr->ProcessAttach(clientFd, addrClient, false);
                    if (!result2)
                    {
                        m_LastMsg = result2.ErrDesc();
                        return -1;
                    }
                    CPCAutoObj autoClose([this]() {m_hDataSocketPtr->XClose(); });

                    result2 = func(m_hDataSocketPtr);
                    if (!result2)
                    {
                        m_LastMsg = result2.ErrDesc();
                        return -1;
                    }
                }

                if (bTransfered)
                    return retCode;
                return  this->FtpCmdOnce_Only(NULL, NULL, 3000);
            }
            return -1;
        }
    }

    /**
    *@brief     发送并接收接收一个包，如果未登录，会自动登录
    *@param     cmd         [in]    命令数据，如果为空，表示不发只收
    *@param     param       [in]    命令参数，可以为空
    *@param     nTimeOut    [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    ftp结果码（如果其他错误，结果码为-1），其他更详细的信息在m_LastMsg
    */
    int FtpCmdOnce(const char* cmd, const char* param, int nTimeOut)
    {
        auto result = FtpCmdOnce_Only(cmd, param, nTimeOut);
        if (result < 0)
        {
            //执行失败，则重新登录
            result = FtpReLogin_Only(nTimeOut);
            if (result < 0)
                return result;

            //登录成功后，再执行一遍
            return FtpCmdOnce_Only(cmd, param, nTimeOut);
        }
        return result;
    }

    /**
    *@brief     用缓存的登录信息（重新）登录
    *@param     nTimeOut    [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    ftp结果码（如果其他错误，结果码为-1），其他更详细的信息在m_LastMsg
    */
    int FtpReLogin_Only(int nTimeOut)
    {
        //如果不成功且原因是服务器断开了连接，则重新登录
        CPCSockAddr addr;
        auto result2 = addr.GetHostByName(m_host.c_str(), m_port);
        if (!result2)
        {
            m_LastMsg = result2.ErrDesc();
            return -1;
        }

        //连接
        auto result3 = m_hCmdSocketPtr->XConnect(addr, nTimeOut, false);
        if (!result3)
        {
            m_LastMsg = result2.ErrDesc();
            return -1;
        }
        m_LocalIp = result3.Get().first;
        int retCode = FtpCmdOnce_Only(NULL, NULL, nTimeOut);
        if (PCFTPCODE_SERVNEWUSER != retCode)
        {
            return retCode;
        }

        //发送用户名
        retCode = FtpCmdOnce_Only("USER", m_username.c_str(), nTimeOut);
        if (retCode < 0)
        {
            return retCode;
        }

        //需要密码时，发送密码
        if (PCFTPCODE_NEEDPASS == retCode)
        {
            retCode = FtpCmdOnce_Only("PASS", m_password.c_str(), nTimeOut);
            if (retCode < 0)
            {
                return retCode;
            }
        }
        //需要账号时，发送账号
        if (PCFTPCODE_NEEDACCOUNT == retCode)
        {
            retCode = FtpCmdOnce_Only("ACCT", m_account.c_str(), nTimeOut);
            if (retCode < 0)
            {
                return retCode;
            }
        }
        if (PCFTPCODE_LOGINOK != retCode)
        {
            return retCode;
        }

        //登录完成，记录当前路径
        retCode = FtpCmdOnce("PWD", "", nTimeOut);
        if (retCode / 100 != 2)
        {
            return retCode;
        }
        std::string dir;
        const char* pEnd = CPCStrUtil::PCGetMiddleFiled(m_LastMsg.c_str(), "\"", "\"", dir);
        if (pEnd == NULL || dir.empty())
        {
            return -1;
        }
        this->FtpCWD(dir.c_str());
        return retCode;
    }

    /**
    *@brief     发送并接收接收一个包
    *@param     cmd         [in]    命令数据，如果为空，表示不发只收
    *@param     param       [in]    命令参数，可以为空
    *@param     nTimeOut    [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    ftp结果码（如果其他错误，结果码为-1），其他更详细的信息在m_LastMsg
    */
    int FtpCmdOnce_Only(const char* cmd, const char* param, int nTimeOut)
    {
        m_LastMsg.clear();
        if (cmd != NULL && cmd[0] != 0)
        {
            m_hCmdSocketPtr->XSend(cmd, strlen(cmd), nTimeOut);
            if (param != NULL && param[0] != 0)
            {
                m_hCmdSocketPtr->XSend(" ", 1, nTimeOut);
                m_hCmdSocketPtr->XSend(param, strlen(param), nTimeOut);
            }
            auto result2 = m_hCmdSocketPtr->XSend("\r\n", 2, nTimeOut);
            if (!result2)
            {
                m_LastMsg = result2.ErrDesc();
                return -1;
            }
        }

        CPCBuffer all(MAX_PATH);
        CPCBuffer buf(MAX_PATH);
        int nRetCode = -1;
        while (1)
        {
            auto result2 = m_hCmdSocketPtr->XRecv(nTimeOut, buf);
            if (!result2)
            {
                m_LastMsg = result2.ErrDesc();
                return -1;
            }
            all.append(buf);

            //不完整，继续读
            if (all.length() < 5 ||
                all[all.length() - 2] != '\r' ||
                all[all.length() - 1] != '\n')
            {
                continue;
            }

            //解析第一行retcode
            if (nRetCode == -1)
            {
                auto result3 = all.toInt(0, 3);
                if (!result3)
                {
                    m_LastMsg = all.c_str();
                    return -1;
                }
                nRetCode = result3.Get();
            }

            //一行回应，直接回复
            if (all[3] != '-')
            {
                m_LastMsg = all.c_str();
                return nRetCode;
            }

            //多行，判断是不是达到最后一行
            for (size_t i = 0; i < all.length() - 5; i++)
            {
                if (all[i] == '\r' && all[i + 1] == '\n')
                {
                    auto result3 = all.toInt(i + 2, 3);
                    if (result3 && result3.Get() == nRetCode && all[i + 5] == ' ')
                    {
                        //是最后一行
                        m_LastMsg = all.c_str();
                        return nRetCode;
                    }
                }
            }

            //不是最后一行，需要继续收
            continue;
        }
    }

protected:
    CPCSocketHandlePtr m_hCmdSocketPtr;
    CPCSocketHandlePtr m_hDataSocketPtr;
    bool m_bPortMode;
    unsigned short m_nPortMix;
    unsigned short m_nPortMax;

    std::string m_LocalIp;
    std::string m_LastMsg;
};

/*********************************************************************/
//seg12 可选功能：ssh和sftp功能封装，需要用户同时定义三个宏：PCHAVE_OPENSSL PCHAVE_LIBSSH2  LIBSSH2_OPENSSL
/*********************************************************************/
#if defined (PCHAVE_OPENSSL) && defined  (PCHAVE_LIBSSH2)
#ifndef LIBSSH2_OPENSSL
#define LIBSSH2_OPENSSL
#endif
#include "libssh2.h"
#include "libssh2_sftp.h"

/**
*@brief     sFtp客户端类
*           依赖于网络功能，所以使用前需要调用CPCPoller::obj().Start();
*/
class CPCSftpClient : public CPCFtpClientBase
{
public:
    /**
    *@brief        构造sftp客户端
    */
    explicit CPCSftpClient()
        :m_hSocketPtr(std::make_shared<CPCSocketHandle>(true, &CPCPoller::obj()))
        , m_sshSession(NULL)
        , m_sftpSession(NULL)
    {
    }
    ~CPCSftpClient()
    {
        FtpClose();
    }

    /**
    *@brief     关闭
    */
    void FtpClose() override
    {
        if (m_sftpSession)
        {
            libssh2_sftp_shutdown(m_sftpSession);
            m_sftpSession = NULL;
        }
        if (m_sshSession)
        {
            libssh2_session_disconnect(m_sshSession, "Normal Shutdown");
            libssh2_session_free(m_sshSession);
            m_sshSession = NULL;
        }
        if (m_hSocketPtr)
            m_hSocketPtr->XClose();
    }

    /**
    *@brief     登录sftp服务器
    *@param     pszHost      [in]    ftp服务器ip地址
    *@param     nPort        [in]    ftp服务器命令连接端口
    *@param     pszUser      [in]    用户名 anonymous
    *@param     pszPwd       [in]    密码
    *@param     pszAcc       [in]    账户，一般不必给出。某些站点可能需要账户来登录
    *@param     nTimeOut     [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。 所有动作全部成功才会返回成功
    */
    CPCResult<> FtpLogin(const char *pszHost = "localhost", unsigned short nPort = 22,
        const char* pszUser = "anonymous", const char* pszPwd = "", const char* pszAcc = "",
        int nTimeOut = 30000) override
    {
        CPCSockAddr addr;
        auto result = addr.GetHostByName(pszHost, nPort);
        if (!result)
        {
            return result;
        }
        if (pszUser == NULL || pszPwd == NULL)
        {
            return result.SetFail("invalid params");
        }

        //socket连接
        FtpClose();
        auto result2 = m_hSocketPtr->XConnect(addr, nTimeOut, false);
        if(!result2)
        {
            return result.SetFail("PCSeperatePath fail:%s",result2.ErrDesc());
        }

        //ssh连接
        m_sshSession = libssh2_session_init();
        if (!m_sshSession)
        {
            return result.SetFail("libssh2_session_init fail.");
        }

        //ssh握手
        int rc = libssh2_session_handshake(m_sshSession, m_hSocketPtr->GetSocket().m_socket);
        if (rc)
        {
            return result.SetFail(rc, "libssh2_session_handshake fail.");
        }
        auto fingerprint = libssh2_hostkey_hash(m_sshSession, LIBSSH2_HOSTKEY_HASH_SHA1);
        auto sign = CPCStrUtil::PCBytes2Dsp(fingerprint, PCSHA1_LEN);

        //ssh认证
        rc = libssh2_userauth_password(m_sshSession, pszUser, pszPwd);
        if (rc)
        {
            return result.SetFail(rc, "libssh2_userauth_password fail.");
        }

        //基于ssh初始化sftp
        m_sftpSession = libssh2_sftp_init(m_sshSession);
        if (!m_sshSession)
        {
            return result.SetFail("libssh2_sftp_init fail.");
        }

        //将ssh设置为阻塞模式
        libssh2_session_set_blocking(m_sshSession, 1);

        //获取当前目录
        char szRealPath[MAX_PATH] = { 0 };
        rc = libssh2_sftp_realpath(m_sftpSession, "./", szRealPath, sizeof(szRealPath));
        if (rc < 0)
        {
            return result.SetFail(rc, "libssh2_sftp_realpath fail.");
        }
        this->FtpCWD(szRealPath);
        return result;
    }

    /**
    *@brief     创建远程目录。目录如果已经存在直接返回成功
    *@param     pszRemotePath    [in]    远程目录路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpMakeDir(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        //文件夹是否已经存在
        CPCResult<> result;
        if (pszRemotePath == NULL || pszRemotePath[0] == 0)
        {
            return result.SetFail("invalid params");
        }
        std::string strRemoteAbsPath = this->FtpGetAbsPath(pszRemotePath, true);
        LIBSSH2_SFTP_ATTRIBUTES attrs;
        int rc = libssh2_sftp_lstat(m_sftpSession, pszRemotePath, &attrs);
        if (rc < 0)
        {
            //创建子目录
            for (size_t i = 1; i < strRemoteAbsPath.length() - 1; i++)
            {
                if (strRemoteAbsPath[i] != '/' && strRemoteAbsPath[i] != '\\')
                {
                    continue;
                }

                //判断是否存在
                rc = libssh2_sftp_stat_ex(m_sftpSession, strRemoteAbsPath.c_str(), i + 1, LIBSSH2_SFTP_LSTAT, &attrs);
                if (rc < 0)
                {
                    rc = libssh2_sftp_mkdir_ex(m_sftpSession, strRemoteAbsPath.c_str(), i + 1, 0755);
                    if (rc < 0)
                    {
                        return result.SetFail(rc, "libssh2_sftp_mkdir fail.");
                    }
                }
            }

            //创建当前目录
            rc = libssh2_sftp_mkdir(m_sftpSession, strRemoteAbsPath.c_str(), 0755);
            if (rc < 0)
            {
                return result.SetFail(rc, "libssh2_sftp_mkdir fail.");
            }
        }
        return result;
    }

    /**
    *@brief     删除远程空目录。
    *@param     pszRemotePath    [in]    远程目录路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpDelDir(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        //文件夹是否已经存在
        CPCResult<> result;
        if (pszRemotePath == NULL)
        {
            return result.SetFail("invalid params");
        }
        std::string strRemoteAbsPath = this->FtpGetAbsPath(pszRemotePath, true);
        LIBSSH2_SFTP_ATTRIBUTES attrs;
        int rc = libssh2_sftp_lstat(m_sftpSession, strRemoteAbsPath.c_str(), &attrs);
        if (rc == 0)
        {
            rc = libssh2_sftp_rmdir(m_sftpSession, strRemoteAbsPath.c_str());
            if (rc < 0)
            {
                return result.SetFail(rc, "libssh2_sftp_rmdir fail.");
            }
        }
        return result;
    }

    /**
    *@brief     删除远程文件。
    *@param     pszRemotePath    [in]    远程文件路径，支持绝对和相对路径
    *@param     nTimeOut         [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpDelFile(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        //文件是否已经存在
        CPCResult<> result;
        if (pszRemotePath == NULL)
        {
            return result.SetFail("invalid params");
        }
        std::string strRemoteAbsPath = this->FtpGetAbsPath(pszRemotePath, false);
        LIBSSH2_SFTP_ATTRIBUTES attrs;
        int rc = libssh2_sftp_lstat(m_sftpSession, strRemoteAbsPath.c_str(), &attrs);
        if (rc == 0)
        {
            rc = libssh2_sftp_unlink(m_sftpSession, strRemoteAbsPath.c_str());
            if (rc < 0)
            {
                return result.SetFail(rc, "libssh2_sftp_unlink fail.");
            }
        }
        return result;
    }

    /**
    *@brief     获取远程文件列表
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     pszRemotePath   [in]    远程文件路径，如果为空，则用当前路径
    *@return    是否成功。
    */
    CPCResult<std::string> FtpList(const char* pszRemotePath, int nTimeOut = 30000) override
    {
        CPCResult<std::string> result;
        std::string path = this->FtpGetAbsPath(pszRemotePath, true);
        auto sftp_handle = libssh2_sftp_opendir(m_sftpSession, path.c_str());
        if (!sftp_handle)
        {
            return result.SetFail("libssh2_sftp_opendir fail.");
        }
        CPCAutoObj autoClose([&sftp_handle](){libssh2_sftp_closedir(sftp_handle); });

        std::string ret;
        do
        {
            char filename[MAX_PATH];
            char longentry[MAX_PATH * 2];
            LIBSSH2_SFTP_ATTRIBUTES attrs;

            int rc = libssh2_sftp_readdir_ex(sftp_handle, filename, sizeof(filename), longentry, sizeof(longentry), &attrs);
            if (rc > 0)
            {
                if ((rc == 1 && filename[0] == '.') ||
                    (rc == 2 && filename[0] == '.'  && filename[1] == '.'))
                    continue;
                if (longentry[0] != '\0')
                {
                    ret += longentry;
                    ret += "\n";
                }
            }
            else if (rc == 0)
            {
                break;
            }
            else
            {
                return result.SetFail(rc, "libssh2_sftp_readdir_ex fail.");
            }
        } while (1);
        return result.SetSucc(std::move(ret));
    }

    /**
    *@brief     下载远程文件到本地
    *@param     pszRemotePath   [in]    远程文件路径
    *@param     pszLocalPath    [in]    本地文件路径
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     nFileSize       [in]    要传输的远程文件大小，传入0表示自动获取
    *@return    是否成功。
    */
    CPCResult<> FtpGet(const char* pszRemotePath, const char* pszLocalPath, int nTimeOut = 30000, uint64_t nFileSize = 0) override
    {
        //新增创建文件目录
        auto result = CPCFileUtil::PCCheckAndCreateDir(pszLocalPath);
        if (!result)
        {
            return result;
        }
        if (pszRemotePath == NULL)
        {
            return result.SetFail("invalid params");
        }
        FILE* fp = fopen(pszLocalPath, "wb");
        if (fp == NULL)
        {
            return result.SetSystemFail();
        }
        CPCAutoObj autoFile([&fp](){PCCloseFILE(fp); });

        std::string path = this->FtpGetAbsPath(pszRemotePath, false);
        auto sftp_handle = libssh2_sftp_open(m_sftpSession, path.c_str(), LIBSSH2_FXF_READ, 0);
        if (!sftp_handle)
        {
            return result.SetFail("libssh2_sftp_open fail.");
        }
        CPCAutoObj autoClose([&sftp_handle](){libssh2_sftp_close(sftp_handle); });

        do
        {
            char mem[PC_MAXPACK];
            int rc = libssh2_sftp_read(sftp_handle, mem, sizeof(mem));
            if (rc > 0)
            {
                result = CPCFileUtil::PCWriteFile(fp, mem, rc);
                if (!result)
                {
                    return result;
                }
            }
            else if (rc == 0)
            {
                break;
            }
            else
            {
                return result.SetFail(rc, "libssh2_sftp_read fail.");
            }
        } while (1);
        return result;
    }

    /**
    *@brief     上传本地文件
    *@param     pszLocalPath    [in]    本地文件路径
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@param     pszRemotePath   [in]    远程文件路径，如果为空，则用本地文件名上传到远程目录的当前路径
    *@return    是否成功。
    */
    CPCResult<> FtpPut(const char* pszLocalPath, const char* pszRemotePath, int nTimeOut = 30000) override
    {
        //新增创建文件目录
        CPCResult<> result;
        auto result2 = CPCFileUtil::PCSeperatePath(pszLocalPath);
        if(!result2)
        {
            return result.SetFail("PCSeperatePath fail:%s",result2.ErrDesc());
        }
        if (::access(pszLocalPath, F_OK) != 0)
        {
            return result.SetSystemFail();
        }

        std::string remoteAbsPath = this->FtpGetAbsPath(pszRemotePath, false);
        if (!remoteAbsPath.empty())
        {
            auto res_remote = CPCFileUtil::PCSeperatePath(remoteAbsPath.c_str());
            if (!res_remote)
            {
                return result;
            }
            FtpMakeDir(res_remote.Get().first.c_str(), nTimeOut);
        }

        auto sftp_handle = libssh2_sftp_open(m_sftpSession, remoteAbsPath.c_str(),
            LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC,
            LIBSSH2_SFTP_S_IRUSR | LIBSSH2_SFTP_S_IWUSR | LIBSSH2_SFTP_S_IRGRP | LIBSSH2_SFTP_S_IROTH);
        if (!sftp_handle)
        {
            return result.SetFail("libssh2_sftp_open(%s) fail.", remoteAbsPath.c_str());
        }
        CPCAutoObj autoClose([&sftp_handle](){libssh2_sftp_close(sftp_handle); });

        return CPCFileUtil::PCScanFile(pszLocalPath, [&sftp_handle, &remoteAbsPath]
            (const char* buf, size_t len)->CPCResult<>
        {
            CPCResult<> res;
            size_t written = 0;
            do
            {
                int rc = libssh2_sftp_write(sftp_handle, buf + written, len - written);
                if (rc < 0)
                {
                    return res.SetFail("libssh2_sftp_write(%s) fail(rc=%d).", remoteAbsPath.c_str(), rc);
                }
                written += rc;
            } while (written < len);
            return res;
        }, 30000);
    }

    /**
    *@brief     下载远程目录到本地
    *@param     pszRemotePath   [in]    远程目录路径
    *@param     pszLocalPath    [in]    本地目录路径
    *@param     callback        [in]    操作文件后回调
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpGetDir(const char* pszRemotePath, const char* pszLocalPath, PCFtpCallBack callback, int nTimeOut = 30000) override
    {
        CPCResult<> result;
        std::string remoteAbsPath = this->FtpGetAbsPath(pszRemotePath, true);
        std::string strLocalPath = (pszLocalPath == NULL) ? "" : pszLocalPath;
        CPCFileUtil::PCDirFormat(strLocalPath);
        CPCFileUtil::PCCreateDir(strLocalPath.c_str());

        auto sftp_handle = libssh2_sftp_opendir(m_sftpSession, remoteAbsPath.c_str());
        if (!sftp_handle)
        {
            return result.SetFail("libssh2_sftp_opendir(%s) fail.", remoteAbsPath.c_str());
        }
        CPCAutoObj autoClose([&sftp_handle](){libssh2_sftp_closedir(sftp_handle); });

        do
        {
            char filename[MAX_PATH];
            char longentry[MAX_PATH * 2];
            LIBSSH2_SFTP_ATTRIBUTES attrs;

            int rc = libssh2_sftp_readdir_ex(sftp_handle, filename, sizeof(filename), longentry, sizeof(longentry), &attrs);
            if (rc > 0)
            {
                if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
                    continue;

                std::string remoteFile = remoteAbsPath + filename;
                std::string localFile = strLocalPath + filename;

                if (LIBSSH2_SFTP_S_ISDIR(attrs.permissions))
                {
                    result = this->FtpGetDir(remoteFile.c_str(), localFile.c_str(), callback, nTimeOut);
                    if (!result)
                    {
                        return result;
                    }
                }
                else
                {
                    result = this->FtpGet(remoteFile.c_str(), localFile.c_str(), nTimeOut);
                    if (!callback(remoteFile.c_str(), localFile.c_str(), result))
                    {
                        return result;
                    }
                }
            }
            else if (rc == 0)
            {
                break;
            }
            else
            {
                return result.SetFail(rc, "libssh2_sftp_readdir_ex fail.");
            }
        } while (1);
        return result;
    }

    /**
    *@brief     上传本地目录到远程
    *@param     pszLocalPath    [in]    本地目录路径
    *@param     pszRemotePath   [in]    远程目录路径，如果为空，则用本地文件目录上传到远程目录的当前路径
    *@param     callback        [in]    操作文件后回调
    *@param     nTimeOut        [in]    超时毫秒。如果nTimeoutMs < 0 代表永不超时
    *@return    是否成功。
    */
    CPCResult<> FtpPutDir(const char* pszLocalPath, const char* pszRemotePath, PCFtpCallBack callback, int nTimeOut = 30000) override
    {
        std::string remoteAbsPath = this->FtpGetAbsPath(pszRemotePath, true);
        std::string strLocalPath = (pszLocalPath == NULL) ? "" : pszLocalPath;
        CPCFileUtil::PCDirFormat(strLocalPath);
        this->FtpMakeDir(remoteAbsPath.c_str(), nTimeOut);

        return CPCFileUtil::PCScanDir(pszLocalPath, [&](const char* pszPath, const char* pszFileName, bool bDir)->CPCResult<>
        {
            CPCResult<> result;
            if (strLocalPath.length() >= strlen(pszPath))
            {
                return result;
            }
            std::string remoteFile = remoteAbsPath + (pszPath + strLocalPath.length());
            CPCStrUtil::PCReplaceAll(remoteFile, "\\", "/");
            if (!bDir)
            {
                result = FtpPut(pszPath, remoteFile.c_str(), nTimeOut);
                if (!callback(pszPath, remoteFile.c_str(), result))
                {
                    return result;
                }
            }
            else
            {
                result = this->FtpMakeDir(remoteFile.c_str(), nTimeOut);
            }
            return result;
        }, true);
    }

protected:
    CPCSocketHandlePtr  m_hSocketPtr;
    LIBSSH2_SESSION*    m_sshSession;
    LIBSSH2_SFTP*       m_sftpSession;
};


#endif    //PCHAVE_OPENSSL && PCHAVE_LIBSSH2  



#endif    //___PCLIB_S_FTP___